feat: add loading states and refactor to TSX#40
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
There was a problem hiding this comment.
Pull request overview
Standardises async/loading feedback across the app’s button primitives (text buttons, submit buttons, and icon-only buttons), while converting touched components from JSX to TSX to improve type-safety and consistency across higher-impact async flows (auth/profile/listings/chat/media/clipboard).
Changes:
- Refactors
Button/SubmitButton/IconButtonto support consistent loading/disabled semantics and accessible labeling, and converts them to TSX with typed props. - Updates key async UX flows (profile actions, listings write/delete, chat send, media upload/delete, clipboard copy) to own pending state correctly and reflect it in the appropriate button primitive.
- Enhances confirmation dialogs (
ButtonToDialog) to support configurable loading labels and prevent double-submit for clientonSubmitconfirmations.
Reviewed changes
Copilot reviewed 16 out of 28 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/components/Button/Button.tsx | Adds typed props + standardised loading/disabled behavior for the base button primitive (button vs link). |
| src/components/SubmitButton/SubmitButton.tsx | Implements loading state via useFormStatus() and compatibility aliasing (pendingText → loadingText). |
| src/components/IconButton/IconButton.tsx | Adds icon-button loading spinner behavior and accessible label handling. |
| src/components/ButtonToDialog/ButtonToDialog.tsx | Adds confirm loading text support + local pending state for async onSubmit confirmations. |
| src/components/ListingPhotosManager/ListingPhotosManager.tsx | Adds per-photo delete loading and upload loading feedback; introduces drag/drop typing. |
| src/components/ListingWrite/ListingWrite.tsx | Improves async lifecycle correctness (validation/reset, navigation-aware loading) and TS typing. |
| src/components/ChatWindow/ChatWindow.tsx | Moves sending state ownership into ChatWindow and wires through to the composer. |
| src/components/ChatComposer/ChatComposer.tsx | Disables composer controls during send and uses IconButton loading label/spinner. |
| src/components/ProfileActions/ProfileActions.tsx | Adds sign-out loading feedback and confirm-loading text for destructive dialogs. |
| src/components/ProfileAccountSettings/ProfileAccountSettings.tsx | Adds explicit loading wiring for submit actions and improves TS typing. |
| src/components/EmailSelector/EmailSelector.tsx | Adds “copying/copied/error” async copy feedback via button loading. |
| src/components/AvatarUploadView/AvatarUploadView.tsx | Adds upload/delete busy handling + button loading text. |
| src/app/(forms)/forgot-password/page.tsx | Updates submit button pending copy (“Emailing...”). |
| src/app/(forms)/profile/reset-password/page.tsx | Updates submit button pending copy (“Resetting...”). |
| src/**/index.ts | Adds barrel exports for updated components. |
Comments suppressed due to low confidence (5)
src/components/Button/Button.tsx:234
- For the Link-rendering branch,
aria-disabled/tabIndexdon’t prevent mouse clicks, so a “disabled” Button withhrefwill still navigate. Alsodisabledisn’t a valid attribute for anchors/NextLink. Consider omittinghrefwhenisDisabledis true, or add anonClickthat callspreventDefault()(and maybestopPropagation()) when disabled, while keeping styling via a data-attribute.
src/components/ListingPhotosManager/ListingPhotosManager.tsx:173 handleDropbuildsnewPhotosfrom the capturedphotosarray after async compression/uploads. If photos are reordered/deleted while the upload is in flight, this can drop those updates. Prefer functional state updates (e.g.,setPhotos(prev => [...prev, ...newFilenames])) and derive the value passed toonPhotosChangefrom that same update, or temporarily disable reordering/deletes while uploading.
src/components/ListingPhotosManager/ListingPhotosManager.tsx:216- In the delete error path,
setPhotos(photos)reverts to the stale pre-delete array captured in the closure, which can overwrite legitimate changes that happened while the delete request was in flight (e.g., uploads/reordering). Consider restoring just the deleted filename (if missing) or tracking a snapshot in a ref, and/or disabling other photo mutations whiledeletingPhotois set.
src/components/EmailSelector/EmailSelector.tsx:124 - This introduces a non-translated UI string (
"Copy failed") in a component that otherwise usesnext-intlmessages. Consider adding aContact.copyButton.copyFailed(or similar) key in the locale message files and usingt(...)here so non-English locales don’t show an English fallback.
src/components/ProfileAccountSettings/ProfileAccountSettings.tsx:208 - Typo in function name:
handleNewslettePreferenceUpdate(missing “r”). Renaming tohandleNewsletterPreferenceUpdatewill improve clarity and avoid propagating the typo to future references.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| : ariaLabel || iconLabels[icon]; | ||
|
|
||
| const handleClick: React.MouseEventHandler<HTMLElement> = (e) => { | ||
| if (loading || disabled) return; |
There was a problem hiding this comment.
Pull request overview
This PR standardizes async/loading feedback across the app’s button primitives (text vs icon buttons) and refactors touched UI components from JSX to TSX, while also tightening async lifecycle handling (preventing double-submits and “stuck” pending states).
Changes:
- Introduces/updates button primitives (
Button,SubmitButton,IconButton,ButtonToDialog) to provide consistent loading/disabled/ARIA behavior. - Refactors key UI flows (profile actions/settings, listing write + media managers, chat send, email copy) to own async state correctly and reflect it in buttons.
- Adds i18n strings and performs small formatting/TSX conversions in Supabase function templates and utility scripts.
Reviewed changes
Copilot reviewed 28 out of 40 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| supabase/functions/send-email-for-auth-action/index.ts | Formatting-only refactor for readability in webhook email function. |
| supabase/functions/_templates/sign-up-email.tsx | Template formatting; no behavioral change. |
| supabase/functions/_templates/reset-password-email.tsx | Template formatting; no behavioral change. |
| supabase/functions/_templates/magic-link-email.tsx | Template formatting; no behavioral change. |
| supabase/functions/_templates/invite-email.tsx | Template formatting; no behavioral change. |
| supabase/functions/_templates/email-change-email.tsx | Template formatting; no behavioral change. |
| src/proxy.ts | Formatting-only changes around cookie initialization. |
| src/components/SubmitButton/SubmitButton.tsx | New TSX SubmitButton integrating useFormStatus() and explicit loading state. |
| src/components/SubmitButton/SubmitButton.jsx | Removes old JSX implementation. |
| src/components/SubmitButton/index.ts | Adds barrel exports for SubmitButton. |
| src/components/ProfileActions/ProfileActions.tsx | Adds sign-out loading state + confirm loading text for destructive action. |
| src/components/ProfileActions/index.ts | Adds barrel exports for ProfileActions. |
| src/components/ProfileAccountSettings/ProfileAccountSettings.tsx | TS typing improvements + wiring SubmitButton loading to update states. |
| src/components/ProfileAccountSettings/index.ts | Adds barrel exports for ProfileAccountSettings. |
| src/components/ListingWrite/ListingWrite.tsx | Refactors to TSX, fixes async submission lifecycle, adds confirm loading text for delete. |
| src/components/ListingWrite/index.ts | Adds barrel exports for ListingWrite. |
| src/components/ListingPhotosManager/ListingPhotosManager.tsx | Adds per-photo delete loading, prevents concurrent mutations, and refactors async flows. |
| src/components/ListingPhotosManager/index.ts | Adds barrel exports for ListingPhotosManager. |
| src/components/IconButton/index.ts | Adds barrel exports for IconButton. |
| src/components/IconButton/IconButton.tsx | New TSX IconButton with spinner replacement + accessible loading label support. |
| src/components/IconButton/IconButton.jsx | Removes old JSX implementation. |
| src/components/EmailSelector/index.ts | Adds barrel exports for EmailSelector. |
| src/components/EmailSelector/EmailSelector.tsx | Adds async copy feedback (copying/copied/error) and button loading text. |
| src/components/ChatWindow/index.ts | Adds barrel exports for ChatWindow. |
| src/components/ChatWindow/ChatWindow.tsx | Adds isSending state, prevents double-send, and threads loading through to composer. |
| src/components/ChatComposer/index.ts | Adds barrel exports for ChatComposer. |
| src/components/ChatComposer/ChatComposer.tsx | Replaces text submit with IconButton loading spinner and proper aria labels/disable rules. |
| src/components/ButtonToDialog/index.ts | Adds barrel exports for ButtonToDialog. |
| src/components/ButtonToDialog/ButtonToDialog.tsx | Adds confirm loading text support + local pending state for async onSubmit. |
| src/components/Button/index.ts | Adds barrel exports for Button. |
| src/components/Button/Button.tsx | Refactors to TSX and standardizes loading behavior (disabled + aria) for real buttons (not links). |
| src/components/AvatarUploadView/index.ts | Adds barrel exports for AvatarUploadView. |
| src/components/AvatarUploadView/AvatarUploadView.tsx | Adds upload/delete pending states and disables controls while busy. |
| src/app/(forms)/profile/reset-password/page.tsx | Adds pending copy (“Resetting…”) via SubmitButton. |
| src/app/(forms)/forgot-password/page.tsx | Adds pending copy (“Emailing…”) via SubmitButton. |
| src/app/(forms)/auth/complete/page.tsx | Minor text formatting only. |
| scripts/seed-local-media.mjs | Formatting-only refactor for readability. |
| messages/es.json | Adds Contact page translations (and copy-state strings). |
| messages/en.json | Adds copy-state strings for EmailSelector (“Copying…”, “Copy failed”). |
| messages/de.json | Adds Contact page translations (and copy-state strings). |
Comments suppressed due to low confidence (3)
src/components/ProfileActions/ProfileActions.tsx:56
handleSignOutwrapssignOutAction()(which redirects) in a try/catch and swallows any thrown error. In Next.js,redirect()is implemented via a thrown redirect error; catching it can prevent the intended navigation and leave the UI stuck in a loading state. Consider removing the catch, or rethrowing redirect errors while only handling genuine failures (and/or switch to a<form action={signOutAction}>+SubmitButtonpattern so the redirect/pending state is handled by the framework).
src/components/ListingPhotosManager/ListingPhotosManager.tsx:198- When an upload fails with a 413, the alert always uses the plural message even if the user only attempted a single file. Since
filesis in scope here, you can pick the singular vs plural message the same way you do for the preflight size check.
src/components/ListingPhotosManager/ListingPhotosManager.tsx:228 - On delete failure, the UI restore appends the deleted photo to the end of the list (
[...currentPhotos, photoToDelete]), which can change ordering compared to the pre-delete state. If ordering matters (and especially with drag-and-drop enabled), capture the original index (or previous array snapshot) before removing and restore it at the same position on failure.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Standardizes async/loading ownership across button primitives (text + icon) and propagates real pending state through higher-impact user flows, while converting touched components from JSX to TSX for stronger typing and consistency.
Changes:
- Introduces standardized loading APIs for
Button,SubmitButton, andIconButton, and threads them through dialogs/forms/chat/media flows. - Refactors several UI components to TSX and adds barrel
index.tsexports for component folders. - Improves async lifecycle handling in a few flows (e.g., prevent double submits, reset pending state on failures).
Reviewed changes
Copilot reviewed 28 out of 40 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| supabase/functions/send-email-for-auth-action/index.ts | Formatting / readability adjustments in the auth email hook function. |
| supabase/functions/_templates/sign-up-email.tsx | Minor JSX formatting cleanup in email template. |
| supabase/functions/_templates/reset-password-email.tsx | Minor JSX formatting cleanup in email template. |
| supabase/functions/_templates/magic-link-email.tsx | Minor JSX formatting cleanup in email template. |
| supabase/functions/_templates/invite-email.tsx | Minor JSX formatting cleanup in email template. |
| supabase/functions/_templates/email-change-email.tsx | Minor JSX formatting cleanup in email template. |
| src/proxy.ts | Formatting-only change in cookie-setting condition/block. |
| src/components/SubmitButton/SubmitButton.tsx | New TSX SubmitButton using useFormStatus() + explicit loading support. |
| src/components/SubmitButton/SubmitButton.jsx | Removes old JSX implementation of SubmitButton. |
| src/components/SubmitButton/index.ts | Adds barrel exports for SubmitButton. |
| src/components/ProfileActions/ProfileActions.tsx | Switches sign-out to server-action form submit + loading text; adds confirm loading text for delete. |
| src/components/ProfileActions/index.ts | Adds barrel exports for ProfileActions. |
| src/components/ProfileAccountSettings/ProfileAccountSettings.tsx | TS typing improvements + wires SubmitButton loading state for profile updates. |
| src/components/ProfileAccountSettings/index.ts | Adds barrel exports for ProfileAccountSettings. |
| src/components/ListingWrite/ListingWrite.tsx | Improves submit/delete async handling, typing, and wires dialog confirm loading copy. |
| src/components/ListingWrite/index.ts | Adds barrel exports for ListingWrite. |
| src/components/ListingPhotosManager/ListingPhotosManager.tsx | Adds fixed-footprint loading/disable states for photo upload/delete + safer state updates during mutations. |
| src/components/ListingPhotosManager/index.ts | Adds barrel exports for ListingPhotosManager. |
| src/components/IconButton/index.ts | Adds barrel exports for IconButton. |
| src/components/IconButton/IconButton.tsx | New TSX icon-only button with spinner-on-loading + accessible loading labels. |
| src/components/IconButton/IconButton.jsx | Removes old JSX implementation of IconButton. |
| src/components/EmailSelector/index.ts | Adds barrel exports for EmailSelector. |
| src/components/EmailSelector/EmailSelector.tsx | Adds async copy state (copying/copied/error) and uses Button loading UI. |
| src/components/ChatWindow/index.ts | Adds barrel exports for ChatWindow. |
| src/components/ChatWindow/ChatWindow.tsx | Moves “sending” ownership into ChatWindow, adds pending protections, and threads state to composer. |
| src/components/ChatComposer/index.ts | Adds barrel exports for ChatComposer. |
| src/components/ChatComposer/ChatComposer.tsx | Switches send control to IconButton loading spinner + disables textarea during send. |
| src/components/ButtonToDialog/index.ts | Adds barrel exports for ButtonToDialog. |
| src/components/ButtonToDialog/ButtonToDialog.tsx | Adds confirmLoadingText + local pending state for async onSubmit dialogs. |
| src/components/Button/index.ts | Adds barrel exports for Button. |
| src/components/Button/Button.tsx | Refactors to TSX, adds text-only loading behavior + aria attributes, and forwards refs. |
| src/components/AvatarUploadView/index.ts | Adds barrel exports for AvatarUploadView. |
| src/components/AvatarUploadView/AvatarUploadView.tsx | Adds busy/disable behavior and loading labels for avatar upload/delete actions. |
| src/app/(forms)/profile/reset-password/page.tsx | Adds pendingText to reset-password submit flow. |
| src/app/(forms)/forgot-password/page.tsx | Adds pendingText to forgot-password submit flow. |
| src/app/(forms)/auth/complete/page.tsx | Minor text formatting tweak. |
| scripts/seed-local-media.mjs | Formatting-only changes for readability. |
| messages/es.json | Adds Spanish Contact translations (including copy loading/failure strings). |
| messages/en.json | Adds copying/copyFailed strings for Contact copy button. |
| messages/de.json | Adds German Contact translations (including copy loading/failure strings). |
Comments suppressed due to low confidence (2)
src/components/Button/Button.tsx:37
ButtonPropsis currently defined as an intersection of bothButtonHTMLAttributesandAnchorHTMLAttributes. Whenhrefis provided,...propsis forwarded toStyledLink, which means button-only attributes (e.g.formAction,formMethod,name, etc.) are still type-allowed and can end up rendered on an<a>as invalid/meaningless attributes. Consider changingButtonPropsto a discriminated union based onhref(or separateButtonProps/LinkButtonProps) so TS prevents passing button-only props to link buttons (and vice versa), and so the spread onto<Link>can’t accidentally include form-only attributes.
src/components/ListingWrite/ListingWrite.tsx:231- For residential listings, the first-name update check compares
profile.first_name !== name, butnamecan include transient whitespace while the value you actually send toupdateFirstNameActionisvalidatedName(trimmed). This can trigger unnecessary update calls (and related UI states) when the only difference is whitespace. Consider basing the comparison onvalidatedName(or comparing trimmed values) so the update runs only when the persisted value would actually change.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Standardises button loading and async feedback. Refactors any JSX file touched to TSX.
Summary
Fix button feedback by standardising async state ownership first, then reflecting that state in the right button primitive. Text buttons use text-only loading labels. Icon buttons keep their fixed footprint and replace the icon with a spinner while updating accessible labels.
Public API changes
Button,SubmitButton,IconButton, andButtonToDialog, plus consumers changed for async behaviour.Buttonremains the text-button primitive withloadingandloadingText.loadingdisables real buttons, setsaria-disabled, and setsaria-busy.loadingText; no spinner for text buttons.SubmitButtonusesuseFormStatus()for server-action forms and accepts explicitloading.pendingTextas a compatibility alias forloadingText.Button.IconButtongetsloadingandloadingLabel.ButtonToDialogsupportsconfirmLoadingText.SubmitButton.onSubmitconfirmations get local pending state and prevent double-submit.Implementation changes
finally.Signing out...; delete account dialog showsDeleting....Adding.../Saving...; delete listing dialog showsDeleting...; validation failures restore the button.ChatWindow; composer textarea/send icon disable during send; send icon shows spinner withloadingLabel="Sending...".Button loading; photo deletion tracks the specific filename and showsDeleting...only on that button; avatar upload/delete disables related controls while work is active.Copying..., thenCopied; handle failures without leaving the button stuck.hrefbuttons in this pass.Testing
npm run buildfor TypeScript, JSX-to-TSX, and Next.js validation.npm run format:check; if implementation mode permits formatting, runnpm run format.Notes